#pragma once

#include <iostream>
#include <string>
#include <pthread.h>
#include <fstream>
#include <vector>
#include <sstream>
#include <sys/types.h>
#include <sys/socket.h>
#include <unordered_map>

#include "Socket.hpp"
#include "log.hpp"

const std::string sep = "\r\n";
const std::string homepage = "./wwwroot";

class HttpServer;
class ThreadData
{
public:
    ThreadData(int sockfd, HttpServer *hs)
        : _sockfd(sockfd), _hs(hs)
    {
    }

public:
    int _sockfd;
    HttpServer *_hs;
};

class HttpRequest
{
public:
    void Deserialize(std::string req)
    {
        // 请求行\r\n请求报头\r\n请求报头\r\n\r\n(空行)请求正文\r\n
        while (1)
        {
            size_t pos = req.find(sep);
            if (pos == std::string::npos)
                break;
            std::string line = req.substr(0, pos);
            if (line.empty())
            {
                req.erase(0, sep.size());
                break;
            }
            _header.push_back(line);
            req.erase(0, pos + sep.size());
        }
        _body = req;
    }

    void Parse()
    {
        // 请求方法 url 协议版本\r\n
        std::stringstream ss(_header[0]);
        ss >> _method >> _url >> _version;

        // url -> /a/b/index.html
        _resource_path = homepage;
        if (_url == "/" || _url == "/index.html")
            _resource_path += "/index.html"; // wwwroot/index.html
        else
            _resource_path += _url;

        size_t pos = _url.rfind(".");
        if (pos == std::string::npos)
            _suffix = ".html";
        else
            _suffix = _url.substr(pos);
    }

public:
    std::vector<std::string> _header;
    std::string _body;

    std::string _method;
    std::string _url;
    std::string _version;

    std::string _resource_path;

    std::string _suffix;
};

class HttpServer
{
private:
    std::string ResourcePathToBody(const std::string &resource_path)
    {
        std::ifstream is(resource_path, std::ios_base::binary);
        if (!is.is_open())
        {
            return "";
        }

        is.seekg(0, is.end);
        int len = is.tellg();
        is.seekg(0, is.beg);

        char buffer[len + 1];
        is.read(buffer, sizeof(buffer));

        is.close();
        return buffer;
    }

    std::string SuffixInfo(const std::string &suffix)
    {
        auto iter = _suffix_info.find(suffix);
        if (iter == _suffix_info.end())
            return "text/html";
        else
            return _suffix_info[suffix];
    }

    void Core_Handle(int sockfd)
    {
        char buffer[4096];
        int r = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
        if (r > 0)
        {
            buffer[r] = 0;

            HttpRequest req;
            req.Deserialize(buffer);
            req.Parse();

            bool ok = true;
            std::string body;
            body = ResourcePathToBody(req._resource_path);
            if (body.empty())
            {
                ok = false;
                std::string err_path = homepage;
                err_path += "/err.html";
                body = ResourcePathToBody(err_path);
            }

            std::string status_line;
            if (ok)
                status_line = "http/1.0 200 OK\r\n";
            else
                status_line = "http/1.0 404 Not Found\r\n";

            std::string header;
            header = "Content-Length: ";
            header += std::to_string(body.size());
            header += "\r\n";

            header += "Content-Type: ";
            header += SuffixInfo(req._suffix);
            header += "\r\n";

            header += "Set-Cookie: name=abc&&password=123";
            header += "\r\n";

            std::string blank_line;
            blank_line = "\r\n";

            std::string response;
            response = status_line;
            response += header;
            response += blank_line;
            response += body;

            send(sockfd, response.c_str(), response.size(), 0);
        }
        close(sockfd);
    }

    static void *Handle(void *args)
    {
        pthread_detach(pthread_self());

        ThreadData *td = static_cast<ThreadData *>(args);
        td->_hs->Core_Handle(td->_sockfd);

        delete td;
        return nullptr;
    }

public:
    HttpServer(uint16_t port)
        : _port(port)
    {
        _suffix_info.insert({".html", "text/html"});
        _suffix_info.insert({".png", "image/png"});
    }

    void Start()
    {
        _sock.Socket();
        _sock.Bind(_port);
        _sock.Listen();

        while (1)
        {
            std::string client_ip;
            uint16_t client_port;
            int sockfd = _sock.Accept(&client_ip, &client_port);
            if (sockfd == -1)
                continue;

            ThreadData *td = new ThreadData(sockfd, this);
            pthread_t tid;
            pthread_create(&tid, nullptr, Handle, td);
        }
    }

private:
    uint16_t _port;
    Sock _sock;
    std::unordered_map<std::string, std::string> _suffix_info;
};